﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AutoMapper;
using FileHelpers;
using Ionic.Zip;
using TransitFeedGenerator.Entity;
using TransitFeedGenerator.Enum;
using TransitFeedGenerator.Util;

using System.IO;

namespace TransitFeedGenerator
{
    public class FeedService
    {
        internal static DelimitedFileEngine<Entity.GoogleTransit.Agency> engineAgency;
        internal static DelimitedFileEngine<Entity.GoogleTransit.Stops> engineStops;
        internal static DelimitedFileEngine<Entity.GoogleTransit.Routes> engineRoutes;
        internal static DelimitedFileEngine<Entity.GoogleTransit.Calendar> engineCalendar;
        internal static DelimitedFileEngine<Entity.GoogleTransit.Trips> engineTrip;
        internal static DelimitedFileEngine<Entity.GoogleTransit.StopTimes> engineStopTime;

        #region Public Methods

        /// <summary>
        /// Loads the data for Feed
        /// </summary>
        /// <param name="feed">Source of information to Google Transit Feed</param>
        /// <returns>Stream with Feed content</returns>
        public static FileStream Generate(TransitFeed feed)
        {
            //Initializing the automapper configuration
            MappingInitialize();

            //Catching the objects for generate the data in GTFS format
            var agency = feed.Agency.Select(Mapper.Map<Agency, Entity.GoogleTransit.Agency>).ToList();
            var stops = feed.Stops.Select(Mapper.Map<Stops, Entity.GoogleTransit.Stops>).ToList();
            var calendar = new List<Entity.GoogleTransit.Calendar>(){Mapper.Map<Schedule, Entity.GoogleTransit.Calendar>(feed.Schedules)};
            var routes = new List<Entity.GoogleTransit.Routes>();
            var trips = new List<Entity.GoogleTransit.Trips>();
            var stopTimes = new List<Entity.GoogleTransit.StopTimes>();

            LoadFeedInformation(feed, routes, trips, stopTimes);
            
            return MakeDestinationFile(agency, stops, calendar, routes, trips, stopTimes);           

        }

        /// <summary>
        /// Saves the Feed as Zip File
        /// </summary>
        /// <param name="destination">Path where the Zip File should be saved</param>
        /// <param name="stream">Stream loaded by MakeDestinationFile method</param>
        public static void SaveToFile(String destination, FileStream stream)
        {
            if (File.Exists(destination))
                File.Delete(destination);

            File.Move(stream.Name, destination);
        }
       
        #endregion

        #region Private Methods

        /// <summary>
        /// Load Feed information 
        /// </summary>
        /// <param name="feed">Source for Feed information</param>
        /// <param name="routes">List of Routes</param>
        /// <param name="trips">List of Trips</param>
        /// <param name="stopTimes">List of StopTimes</param>
        private static void LoadFeedInformation(TransitFeed feed, List<Entity.GoogleTransit.Routes> routes, List<Entity.GoogleTransit.Trips> trips, List<Entity.GoogleTransit.StopTimes> stopTimes)
        {
            foreach (var route in feed.Schedules.Routes)
            {
                var newRoute = Mapper.Map<Routes, Entity.GoogleTransit.Routes>(route);
                routes.Add(newRoute);

                foreach (var trip in route.Trips)
                {
                    var newTrip = Mapper.Map<Trip, Entity.GoogleTransit.Trips>(trip);
                    newTrip.route_id = route.Id;
                    newTrip.service_id = feed.Schedules.Id;
                    trips.Add(newTrip);

                    foreach (var stopTime in trip.StopTimes)
                    {
                        var newStopTime = Mapper.Map<StopTime, Entity.GoogleTransit.StopTimes>(stopTime);
                        newStopTime.stop_sequence = (trip.StopTimes.IndexOf(stopTime) + 1).ToString();
                        newStopTime.trip_id = trip.Id;
                        stopTimes.Add(newStopTime);
                    }

                }

            }
        }
        
        /// <summary>
        /// Make Stream with Feed content
        /// </summary>
        /// <param name="agency">Agency's informations</param>
        /// <param name="stops">Stop's informations</param>
        /// <param name="calendar">Calendar's information</param>
        /// <param name="routes">Routes's informations</param>
        /// <param name="trips">Trips's informations</param>
        /// <param name="stopTimes">Stop Times's informations</param>
        /// <returns>Stream with Feed content</returns>
        private static System.IO.FileStream MakeDestinationFile(List<Entity.GoogleTransit.Agency> agency, List<Entity.GoogleTransit.Stops> stops, List<Entity.GoogleTransit.Calendar> calendar, List<Entity.GoogleTransit.Routes> routes, List<Entity.GoogleTransit.Trips> trips, List<Entity.GoogleTransit.StopTimes> stopTimes)
        {
            using (var feedFile = new ZipFile())
            {
                GetEngines(out engineAgency, out engineStops, out engineRoutes, out engineCalendar, out engineTrip, out engineStopTime);

                //Create the files of the feed in memory
                feedFile.AddEntry("agency.txt", "", engineAgency.WriteString(agency));
                feedFile.AddEntry("stops.txt", "", engineStops.WriteString(stops));
                feedFile.AddEntry("routes.txt", "", engineRoutes.WriteString(routes));
                feedFile.AddEntry("calendar.txt", "", engineCalendar.WriteString(calendar));
                feedFile.AddEntry("trips.txt", "", engineTrip.WriteString(trips));
                feedFile.AddEntry("stop_times.txt", "", engineStopTime.WriteString(stopTimes));

               //Creating temp file with feed data
                var destinationFile = File.Create(System.IO.Path.GetTempFileName());

                //Save the feed data
                feedFile.Save(destinationFile);
                destinationFile.Close();

                return destinationFile;
            }
        }

        /// <summary>
        /// Get the FileHelpers engines for generate the destination file
        /// </summary>
        /// <param name="engineAgency">FileHelper engine for Agency File</param>
        /// <param name="engineStops">FileHelper engine for Stops File</param>
        /// <param name="engineRoutes">FileHelper engine for Routes File</param>
        /// <param name="engineCalendar">FileHelper engine for Calendar File</param>
        /// <param name="engineTrip">FileHelper engine for Trips File</param>
        /// <param name="engineStopTime">FileHelper engine for Stop Times File</param>
        private static void GetEngines(out DelimitedFileEngine<Entity.GoogleTransit.Agency> engineAgency, out DelimitedFileEngine<Entity.GoogleTransit.Stops> engineStops, out DelimitedFileEngine<Entity.GoogleTransit.Routes> engineRoutes, out DelimitedFileEngine<Entity.GoogleTransit.Calendar> engineCalendar, out DelimitedFileEngine<Entity.GoogleTransit.Trips> engineTrip, out DelimitedFileEngine<Entity.GoogleTransit.StopTimes> engineStopTime)
        {
            engineAgency = new DelimitedFileEngine<Entity.GoogleTransit.Agency>();
            engineStops = new DelimitedFileEngine<Entity.GoogleTransit.Stops>();
            engineRoutes = new DelimitedFileEngine<Entity.GoogleTransit.Routes>();
            engineCalendar = new DelimitedFileEngine<Entity.GoogleTransit.Calendar>();
            engineTrip = new DelimitedFileEngine<Entity.GoogleTransit.Trips>();
            engineStopTime = new DelimitedFileEngine<Entity.GoogleTransit.StopTimes>();

            //Setting the Header's text for each Engine
            engineAgency.HeaderText = GetHeaderText<Entity.GoogleTransit.Agency>();
            engineStops.HeaderText = GetHeaderText<Entity.GoogleTransit.Stops>();
            engineRoutes.HeaderText = GetHeaderText<Entity.GoogleTransit.Routes>();
            engineCalendar.HeaderText = GetHeaderText<Entity.GoogleTransit.Calendar>();
            engineTrip.HeaderText = GetHeaderText<Entity.GoogleTransit.Trips>();
            engineStopTime.HeaderText = GetHeaderText<Entity.GoogleTransit.StopTimes>();
        }

        /// <summary>
        /// Get Header Text for a destination file according with data type
        /// </summary>
        /// <typeparam name="T">Data Type for catching header text</typeparam>
        /// <returns>String delimited by commas (,) with header text</returns>
        private static string GetHeaderText<T>()
        {
            return String.Join(",", typeof(T).GetFields().Select(x => x.Name).ToArray());
        }

        /// <summary>
        /// Configure the setting for AutoMapper mapping
        /// </summary>
        private static void MappingInitialize()
        {
            //Creating the mapper for type Agency
            MapAgency();

            //Creating the mapper for type Stops
            MapStops();

            //Creating the mapper for type Routes
            MapRoutes();

            //Creating the mapper for type Calendar
            MapCalendar();

            //Creating the mapper for type Trip
            MapTrip();

            //Creating the mapper for type StopTime
            MapStopTime();
        }
        
        #endregion
        
        #region Mapping

        private static void MapStopTime()
        {
            Mapper.CreateMap<StopTime, Entity.GoogleTransit.StopTimes>()
                .ForMember(x => x.stop_id, y => y.MapFrom(o => o.Stop.Id))
                .ForMember(x => x.arrival_time, y => y.MapFrom(o => o.Arrival.ToString("HH:mm:ss")))
                .ForMember(x => x.departure_time, y => y.MapFrom(o => o.Departure.ToString("HH:mm:ss")));
        }

        private static void MapTrip()
        {
            Mapper.CreateMap<Trip, Entity.GoogleTransit.Trips>()
                .ForMember(x => x.trip_id, y => y.MapFrom(o => o.Id))
                .ForMember(x => x.direction_id, y => y.MapFrom(o => ((int)o.Direction).ToString()))
                .ForMember(x => x.trip_headsign, y => y.MapFrom(o => o.Headsign));         
        }

        private static void MapCalendar()
        {
            Mapper.CreateMap<Schedule, Entity.GoogleTransit.Calendar>()
                .ForMember(x => x.service_id, y => y.MapFrom(o => o.Id))
                .ForMember(x => x.monday, y => y.MapFrom(o => o.WeekDaysValids.Contains(DayOfWeek.Monday)?"1":"0"))
                .ForMember(x => x.tuesday, y => y.MapFrom(o => o.WeekDaysValids.Contains(DayOfWeek.Tuesday) ? "1" : "0"))
                .ForMember(x => x.wednesday, y => y.MapFrom(o => o.WeekDaysValids.Contains(DayOfWeek.Wednesday) ? "1" : "0"))
                .ForMember(x => x.thursday, y => y.MapFrom(o => o.WeekDaysValids.Contains(DayOfWeek.Thursday) ? "1" : "0"))
                .ForMember(x => x.friday, y => y.MapFrom(o => o.WeekDaysValids.Contains(DayOfWeek.Friday) ? "1" : "0"))
                .ForMember(x => x.saturday, y => y.MapFrom(o => o.WeekDaysValids.Contains(DayOfWeek.Saturday) ? "1" : "0"))
                .ForMember(x => x.sunday, y => y.MapFrom(o => o.WeekDaysValids.Contains(DayOfWeek.Sunday) ? "1" : "0"))
                .ForMember(x => x.start_date, y => y.MapFrom(o => o.Start.ToString("yyyyMMdd")))
                .ForMember(x => x.end_date, y => y.MapFrom(o => o.End.ToString("yyyyMMdd")));
        }

        private static void MapRoutes()
        {
            Mapper.CreateMap<Routes, Entity.GoogleTransit.Routes>()
                .ForMember(x => x.agency_id, y => y.MapFrom(o => o.Agency.Id))
                .ForMember(x => x.route_desc, y => y.MapFrom(o => o.Description))
                .ForMember(x => x.route_id, y => y.MapFrom(o => o.Id))
                .ForMember(x => x.route_long_name, y => y.MapFrom(o => o.LongName))
                .ForMember(x => x.route_short_name, y => y.MapFrom(o => o.ShortName))
                .ForMember(x => x.route_type, y => y.MapFrom(o => ((int)o.Type).ToString()))
                .ForMember(x => x.route_url, y => y.MapFrom(o => o.Url.ToString()));
        }

        private static void MapStops()
        {
            Mapper.CreateMap<Stops, Entity.GoogleTransit.Stops>()
                .ForMember(x => x.location_type, y => y.MapFrom(o => ((int)o.Type).ToString()))
                .ForMember(x => x.stop_code, y => y.MapFrom(o => o.Code))
                .ForMember(x => x.stop_desc, y => y.MapFrom(o => o.Description))
                .ForMember(x => x.stop_id, y => y.MapFrom(o => o.Id))
                .ForMember(x => x.stop_lat, y => y.MapFrom(o => o.Latitude))
                .ForMember(x => x.stop_lon, y => y.MapFrom(o => o.Longitude))
                .ForMember(x => x.stop_name, y => y.MapFrom(o => o.Name))
                .ForMember(x => x.stop_url, y => y.MapFrom(o => o.Url.ToString()))
                .ForMember(x => x.zone_id, y => y.MapFrom(o => o.ZoneId));
        }

        private static void MapAgency()
        {
            Mapper.CreateMap<Agency, Entity.GoogleTransit.Agency>()
                .ForMember(x => x.agency_id, y => y.MapFrom(o => o.Id))
                .ForMember(x => x.agency_lang, y => y.MapFrom(o => o.Language.GetEnumName<Languages>()))
                .ForMember(x => x.agency_name, y => y.MapFrom(o => o.Name))
                .ForMember(x => x.agency_phone, y => y.MapFrom(o => o.Phone))
                .ForMember(x => x.agency_timezone, y => y.MapFrom(o => o.TimeZone.GetEnumName<TimeZones>()))
                .ForMember(x => x.agency_url, y => y.MapFrom(o => o.Url.ToString()));
        }

#endregion

    }
}
